package com.szeldon.declatest.eval.plugins;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.szeldon.declatest.eval.IScenarioEvaluatorPlugin;
import com.szeldon.declatest.eval.exceptions.EvaluationException;
import com.szeldon.declatest.exec.AbstractTestsSuiteExecutor;
import com.szeldon.declatest.exec.TestSuiteExecHistory;

/**
 * Default plugin for values without a prefix (or empty prefix). This is the general scheme for values to be evaluated:
 * 
 * <pre>
 * plugin-compatible-expression@type
 * </pre>
 * 
 * Currently supported types and their syntax:
 * <ul>
 * <li>property: property-name@property</li>
 * <li>step: type-of-value-from-step@step-id@step - currently supported "type-of-value-from-step"s are "request" and
 * "response"</li>
 * <li>function: function-name@function - currently supported "function-name"s are "unixtime-millis", "datetime-14",
 * "random-long", "random-int"</li>
 * </ul>
 * 
 * Currently supported operators:
 * <ul>
 * <li>equals</li>
 * <li>notEquals</li>
 * <li>matches - matches left value to the right regular expression</li>
 * </ul>
 * 
 * @author szeldon
 * 
 */
public class DefaultEvaluatorPlugin implements IScenarioEvaluatorPlugin {

    private static Logger logger = Logger.getLogger(DefaultEvaluatorPlugin.class.getName());

    public static final String SEPARATOR = "@";

    public static final String DEFAULT_EVALUATOR_PLUGIN_PREFIX = "";

    public static final String PROPERTIES_TAG = "property";
    public static final String STEP_TAG = "step";
    public static final String FUNCTION_TAG = "function";

    public static final String RESPONSE_STEP_ELEMENT_TYPE = "response";
    public static final String REQUEST_STEP_ELEMENT_TYPE = "request";

    public static final String UNIX_TIME_FUNCTION_NAME = "unixtime-millis";
    public static final String DATE_TIME_14_FUNCTION_NAME = "datetime-14";
    public static final String RANDOM_LONG_FUNCTION_NAME = "random-long";
    public static final String RANDOM_INT_FUNCTION_NAME = "random-int";

    public static final String EQUALS_OPERATOR = "equals";
    public static final String NOT_EQUALS_OPERATOR = "notEquals";
    public static final String MATCHES_OPERATOR = "matches";

    @Override
    public boolean evaluateOperator(AbstractTestsSuiteExecutor executor, String leftValue, String operator,
            String rightValue, Properties properties, TestSuiteExecHistory history) {

        boolean result = false;

        if (operator.equals(EQUALS_OPERATOR)) {
            if (leftValue.equals(rightValue)) {
                result = true;
            }
        } else if (operator.equals(NOT_EQUALS_OPERATOR)) {
            if (!leftValue.equals(rightValue)) {
                result = true;
            }
        } else if (operator.equals(MATCHES_OPERATOR)) {
            Pattern pattern = Pattern.compile(rightValue);
            Matcher matcher = pattern.matcher(leftValue);
            if (matcher.matches()) {
                result = true;
            }
        } else {
            throw new UnsupportedOperationException("'" + operator + "' operator isn't supported yet.");
        }

        return result;

    }

    @Override
    public String evaluateValue(AbstractTestsSuiteExecutor executor, String value, Properties properties,
            TestSuiteExecHistory history) throws EvaluationException {
        logger.fine("Evaluating value=" + value);

        int lastSeparatorIndex = value.lastIndexOf(SEPARATOR);

        if (lastSeparatorIndex == -1) {
            throw new EvaluationException("Value for evaluation by default plugin must contain '" + SEPARATOR
                    + "' separator. value=" + value);
        }

        String beforeLastSeparator = value.substring(0, lastSeparatorIndex);
        String afterLastSeparator = value.substring(lastSeparatorIndex + 1);

        String result = null;

        if (afterLastSeparator.equals(PROPERTIES_TAG)) {
            result = properties.getProperty(beforeLastSeparator);
        } else if (afterLastSeparator.equals(STEP_TAG)) {
            result = evaluateStep(beforeLastSeparator, value, history);
        } else if (afterLastSeparator.equals(FUNCTION_TAG)) {
            result = evaluateFunction(beforeLastSeparator);
        }

        return result;
    }

    @Override
    public String getPluginPrefix() {
        return DEFAULT_EVALUATOR_PLUGIN_PREFIX;
    }

    /**
     * Evaluates string assuming it is part of an expression being before "@step".
     * 
     * @param beforeLastSeparator
     * @param expression
     * @param history
     * @return
     * @throws EvaluationException
     */
    private String evaluateStep(String beforeLastSeparator, String expression, TestSuiteExecHistory history)
            throws EvaluationException {
        String result = null;

        int beforeSecondLastSeparatorIndex = beforeLastSeparator.lastIndexOf(SEPARATOR);
        if (beforeSecondLastSeparatorIndex == -1) {
            throw new EvaluationException("Step must be preeceded by name of the step and type of event of that step "
                    + "(scheme: event_type@step_name@step). Instead it was: " + expression);
        }
        String stepElementType = beforeLastSeparator.substring(0, beforeSecondLastSeparatorIndex);
        String stepId = beforeLastSeparator.substring(beforeLastSeparator.lastIndexOf(SEPARATOR) + 1);

        if (stepElementType.equals(RESPONSE_STEP_ELEMENT_TYPE)) {
            result = history.getResponseContentForScenarioStep(stepId);
            if (result == null) {
                throw new EvaluationException("There's no response for '" + stepId + "' step ID.");
            }
        } else if (stepElementType.equals(REQUEST_STEP_ELEMENT_TYPE)) {
            throw new UnsupportedOperationException("Request step element isn't implemented yet.");
        }
        result = history.getResponseContentForScenarioStep(stepId);
        return result;
    }

    /**
     * 
     * @param beforeLastSeparator
     * @return
     */
    private String evaluateFunction(String beforeLastSeparator) {
        String result = null;
        if (beforeLastSeparator.equals(UNIX_TIME_FUNCTION_NAME)) {
            result = "" + System.currentTimeMillis();
        } else if (beforeLastSeparator.equals(DATE_TIME_14_FUNCTION_NAME)) {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
            result = formatter.format(new Date());
        } else if (beforeLastSeparator.equals(RANDOM_LONG_FUNCTION_NAME)) {
            Random random = new Random();
            result = "" + random.nextLong();
        } else if (beforeLastSeparator.equals(RANDOM_INT_FUNCTION_NAME)) {
            Random random = new Random();
            result = "" + random.nextInt();
        }

        return result;
    }
}
